﻿namespace Json2Model
{
    public class CodeCompiler: INotifyPropertyChanged
    {
        private CodeGeneratorOptions options = new CodeGeneratorOptions()
        {
            BlankLinesBetweenMembers = true,
            BracingStyle = "C",
            VerbatimOrder = false
        };

        public event PropertyChangedEventHandler? PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public string Namespace { get; set; } = string.Empty;
        public bool BracingStyleIsC { get => options.BracingStyle=="C"; set => options.BracingStyle = value?"C":"B"; }
        public bool BlankLinesBetweenMembers { get => options.BlankLinesBetweenMembers; set => options.BlankLinesBetweenMembers = value; }
        public bool SortByType { get => !options.VerbatimOrder;set => options.VerbatimOrder = !value; }
        private bool isClass = true;
        public bool IsClass
        {
            get => isClass;
            set
            {
                isClass = value;
                OnPropertyChanged();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsStruct"));
            }
        }
        public bool IsStruct { get => !IsClass; set => IsClass = !value; }
        public bool IsSealed { get; set; } = false;
        public string OutputPath { get; set; } = Environment.CurrentDirectory + "\\Models\\";
        public bool AutoImports { get; set; }= true;
        public string ArrayType => JObjectFormater.ArrayType;
        public bool IsSerializable { get; set; } = true;
        public bool IsAddJsonPropAttr { get; set; } = true;
    
        public CodeTypeDeclaration GetCodeTypeDeclaration(string name)
        {
            return new CodeTypeDeclaration(Converter.TypeNameFormat(name))
            {
                IsClass = IsClass,
                IsStruct = IsStruct,
                TypeAttributes = IsSealed ? TypeAttributes.Public | TypeAttributes.Sealed : TypeAttributes.Public
            };
        }
        public CodeMemberField GetCodeMemberField(JAttribute attr)
        {
            CodeMemberField res;
            string fieldName = Converter.FieldNameFormat(attr.Name);
            res = new CodeMemberField(attr.TypeName, fieldName);
            res.Attributes = MemberAttributes.Private;
            return res;
        }
        public CodeMemberProperty GetCodeMemberProperty(JAttribute attr)
        {
            string  prepertyName= Converter.PropertyNameFormat(attr.Name);
            string fieldName = Converter.FieldNameFormat(attr.Name);
            CodeMemberProperty codeMemberPreperty = new CodeMemberProperty();
            codeMemberPreperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            codeMemberPreperty.Name = prepertyName;
            codeMemberPreperty.HasGet = true;
            codeMemberPreperty.HasSet = true;
            codeMemberPreperty.Type = new CodeTypeReference(attr.TypeName);
            //codeMemberPreperty.Comments.Add(new CodeCommentStatement("<summary>", true));
            //codeMemberPreperty.Comments.Add(new CodeCommentStatement("", true));
            //codeMemberPreperty.Comments.Add(new CodeCommentStatement("</summary>", true));
            codeMemberPreperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
            codeMemberPreperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));

            if (IsAddJsonPropAttr)
            {
                codeMemberPreperty.CustomAttributes.Add(new CodeAttributeDeclaration("JsonProperty", new CodeAttributeArgument
                {
                    Name = "PropertyName",
                    Value = new CodeTypeReferenceExpression($"\"{attr.Name}\"")
                }));
            }
            return codeMemberPreperty;
        }
        public string CompileClass(JModel model)
        {
            return CompileClasses(new List<JModel> { model });
        }
        public string CompileClasses(IList<JModel> jModels)
        {
            CodeCompileUnit unit = new CodeCompileUnit();

            CodeNamespace sampleNamespace = new CodeNamespace(Namespace);
            if (AutoImports)
            {
                if (IsAddJsonPropAttr)
                    sampleNamespace.Imports.Add(new CodeNamespaceImport("Newtonsoft.Json"));
                sampleNamespace.Imports.Add(new CodeNamespaceImport("System"));
            }
            unit.Namespaces.Add(sampleNamespace);

            foreach (var JModel in jModels)
            {
                CodeTypeDeclaration codeClass = GetCodeTypeDeclaration(JModel.Name);
                sampleNamespace.Types.Add(codeClass);

                foreach (var attr in JModel.Attributes)
                {
                    codeClass.Members.Add(GetCodeMemberField(attr));
                    codeClass.Members.Add(GetCodeMemberProperty(attr));
                    if (AutoImports && attr.TypeName.Contains(ArrayType))
                    {
                        sampleNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
                    }
                }
                if (IsSerializable)
                {
                    //codeClass.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(SerializableAttribute))));
                    codeClass.CustomAttributes.Add(new CodeAttributeDeclaration("Serializable"));
                }
            }
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            /*
            string className = OutputPath;
            if (JModeles.Count() == 1)
                className += Converter.TypeNameFormat(JModeles[0].Name) + ".cs";
            else
                className += "Models.cs";  
            using StreamWriter sw = new StreamWriter(className);
            */
            StringBuilder sb = new StringBuilder();
            using TextWriter tw = new StringWriter(sb);
            provider.GenerateCodeFromCompileUnit(unit, tw, options);
            return sb.ToString().Replace("@","");
        }
    }
}
